package com.moodair.pip.core;

import com.moodair.pip.core.annotation.InputComponent;
import com.moodair.pip.core.annotation.OutputComponent;
import com.moodair.pip.core.annotation.PluginComponent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;


/**
 * @author MoodAir
 */
public class PipelineRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware, ResourceLoaderAware, EnvironmentAware {
    private final static Logger logger = LoggerFactory.getLogger(PipelineRegistrar.class);
    private ResourceLoader resourceLoader;
    private BeanFactory beanFactory;
    private final Set<String> pipelineNames = new HashSet<>();

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        logger.debug("Searching @InputComponent, @OutputComponent, @PluginComponent");
        PipelineClassPathScanner scanner = new PipelineClassPathScanner(registry, pipelineNames);
        scanner.setBeanNameGenerator(new PipelineBeanNameGenerator());
        try {
            if (this.resourceLoader != null) {
                scanner.setResourceLoader(this.resourceLoader);
            }
            List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
            if (logger.isDebugEnabled()) {
                packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
            }
             scanner.registerFilters(StringUtils.toStringArray(packages));
        } catch (IllegalStateException ex) {
            logger.warn("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
        }
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }


    @Override
    public void setEnvironment(Environment environment) {
        if (!(environment instanceof AbstractEnvironment)) {
            throw new UnsupportedOperationException("environment类型暂未支持：" + environment.getClass().getName());
        }
        final String strWorker = "worker", strPlugin = "plugin", strServer = "pipeline.server.";
        Map<String, Object> properties = new HashMap<>();
        ((AbstractEnvironment) environment).getPropertySources().forEach(source -> {
            if (source instanceof EnumerablePropertySource) {
                for (String name : ((EnumerablePropertySource<?>) source).getPropertyNames()) {
                    if (name.startsWith(strServer)
                            && (name.endsWith(strWorker) || name.endsWith(strPlugin))) {
                        String key = name.substring(strServer.length());
                        if (!properties.containsKey(key)) {
                            properties.put(key, source.getProperty(name));
                        }
                    }
                }
            }
        });
        properties.forEach((name, value) -> {
                String pipelineName = name.substring(0, name.length() - (strWorker.length() + 1));
                Object pluginsObj = properties.get(pipelineName + ".plugin");
                List<String> plugins = new ArrayList<>();
                if (pluginsObj != null) {
                    plugins.addAll(Arrays.asList(pluginsObj.toString().split(",")));
                }
                logger.info("pipeline {} use plugin {}", pipelineName, plugins);
                pipelineNames.add("pipeline.input." + pipelineName);
                pipelineNames.add("pipeline.output." + pipelineName);
                plugins.forEach(e -> pipelineNames.add("pipeline.plugin." + e));
        });
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }


    public static class PipelineBeanNameGenerator implements BeanNameGenerator {
        @Override
        public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
            if (definition instanceof AnnotatedBeanDefinition) {
                AnnotationMetadata metadata = ((AnnotatedBeanDefinition) definition).getMetadata();
                for (String e : metadata.getAnnotationTypes()) {
                    if (InputComponent.class.getName().equals(e)) {
                        definition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
                        Map<String, Object> map = metadata.getAnnotationAttributes(e);
                        Object value = map.get("pipeline");
                        return "pipeline.input." + (value == null ? null : value.toString());
                    } else if (OutputComponent.class.getName().equals(e)) {
                        definition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
                        Map<String, Object> map = metadata.getAnnotationAttributes(e);
                        Object value = map.get("pipeline");
                        return "pipeline.output." + (value == null ? null : value.toString());
                    } else if (PluginComponent.class.getName().equals(e)) {
                        Map<String, Object> map = metadata.getAnnotationAttributes(e);
                        Object value = map.get("value");
                        return "pipeline.plugin." + (value == null ? null : value.toString());
                    }
                }
            }
            return definition.getBeanClassName();
        }
    }

}
